use std::{
	marker::PhantomData,
	ops::{Deref, DerefMut},
};

use crate::{JArrayPrimMaker, JBool, JEnv, JInt, JObject, JSize, ReleaseMode, JFALSE};

pub struct JArray {
	__supper: JObject,
}
impl Deref for JArray {
	type Target = JObject;
	fn deref(&self) -> &Self::Target {
		&self.__supper
	}
}
impl AsRef<JObject> for JArray {
	fn as_ref(&self) -> &JObject {
		&self.__supper
	}
}

pub struct JArrayObject<T: AsRef<JObject>> {
	__supper: JArray,
	__object: JObject,
	__t: PhantomData<T>,
}
impl<T: AsRef<JObject>> Deref for JArrayObject<T> {
	type Target = JArray;
	fn deref(&self) -> &Self::Target {
		&self.__supper
	}
}
impl<T: AsRef<JObject>> AsRef<JArray> for JArrayObject<T> {
	fn as_ref(&self) -> &JArray {
		&self.__supper
	}
}
impl<T: AsRef<JObject>> AsRef<JObject> for JArrayObject<T> {
	fn as_ref(&self) -> &JObject {
		&self.__object
	}
}
impl<T: AsRef<JObject>> JArrayObject<T> {
	pub fn new<'a, 'b>(env: &'a JEnv, size: JSize, init_element: &'b T) -> Option<&'a Self> {
		env.new_array_object(size, init_element.as_ref().class(env)?, init_element.as_ref())
	}
	pub fn get<'a, 'b>(&'b self, env: &'a JEnv, index: JSize) -> Option<&'a T> {
		env.array_object_get(self, index)
	}
	pub fn set(&self, env: &JEnv, index: JSize, value: &T) -> Option<()> {
		env.array_object_set(self, index, value)
	}
}

pub struct JArrayPrim<T: JArrayPrimMaker> {
	__supper: JArray,
	__object: JObject,
	_t: PhantomData<T>,
}
impl<T: JArrayPrimMaker> Deref for JArrayPrim<T> {
	type Target = JArray;
	fn deref(&self) -> &Self::Target {
		&self.__supper
	}
}
impl<T: JArrayPrimMaker> AsRef<JArray> for JArrayPrim<T> {
	fn as_ref(&self) -> &JArray {
		&self.__supper
	}
}
impl<T: JArrayPrimMaker> AsRef<JObject> for JArrayPrim<T> {
	fn as_ref(&self) -> &JObject {
		&self.__object
	}
}
impl<T: JArrayPrimMaker> JArrayPrim<T> {
	pub fn new(env: &JEnv, size: JSize) -> Option<&Self> {
		env.new_array(size)
	}
	pub fn len(&self, env: &JEnv) -> Option<JInt> {
		env.array_len(self.as_ref())
	}
	pub fn lock<'a>(&'a self, env: &'a JEnv) -> Option<JArrayPrimGuard<'a, T>> {
		let mut is_copy: JBool = JFALSE;
		let data = env.lock_array(self, &mut is_copy)?;
		Some(JArrayPrimGuard {
			is_copy,
			is_critical: false,
			is_abort: true,
			array: self,
			env,
			data,
		})
	}
	pub fn lock_critical<'a>(&'a self, env: &'a JEnv) -> Option<JArrayPrimGuard<'a, T>> {
		let mut is_copy: JBool = JFALSE;
		let data = env.lock_array_critical(self, &mut is_copy)?;
		Some(JArrayPrimGuard {
			is_copy,
			is_critical: true,
			is_abort: true,
			array: self,
			env,
			data,
		})
	}
	pub fn replace(&self, env: &JEnv, at: JInt, data: &[T]) -> Option<()> {
		env.replace_array(self, at, data)
	}
	pub fn copy_to(&self, env: &JEnv, at: JInt, data: &mut [T]) -> Option<()> {
		env.copy_array(self, at, data)
	}
}

pub struct JArrayPrimGuard<'a, T: JArrayPrimMaker> {
	is_copy: JBool,
	is_critical: bool,
	is_abort: bool,
	array: &'a JArrayPrim<T>,
	env: &'a JEnv,
	data: &'a mut [T],
}
impl<'a, T: JArrayPrimMaker> Drop for JArrayPrimGuard<'a, T> {
	fn drop(&mut self) {
		let Self {
			array,
			env,
			data,
			is_abort,
			is_critical,
			..
		} = self;
		let mode = if *is_abort { ReleaseMode::ABORT } else { ReleaseMode::COMMIT_FREE };
		if *is_critical {
			env.unlock_array_critical(array, data, mode);
		} else {
			env.unlock_array(*array, data, mode);
		}
	}
}
impl<'a, T: JArrayPrimMaker> Deref for JArrayPrimGuard<'a, T> {
	type Target = [T];
	fn deref(&self) -> &Self::Target {
		self.data
	}
}
impl<'a, T: JArrayPrimMaker> DerefMut for JArrayPrimGuard<'a, T> {
	fn deref_mut(&mut self) -> &mut Self::Target {
		self.commit();
		self.data
	}
}
impl<'a, T: JArrayPrimMaker> JArrayPrimGuard<'a, T> {
	pub fn is_copy(&self) -> JBool {
		self.is_copy
	}
	pub fn commit(&mut self) {
		self.is_abort = false;
	}
	pub fn commit_immediately(&mut self) {
		if self.is_critical {
			self.env.unlock_array_critical(self.array, self.data, ReleaseMode::COMMIT);
		} else {
			self.env.unlock_array(self.array, self.data, ReleaseMode::COMMIT);
		}
		self.is_abort = true;
	}
}
